package com.byx.imclientutil.netty;

import android.util.Log;

import com.byx.imclientutil.config.ImConnectionStatus;

import java.net.URI;

import io.netty.channel.Channel;
import io.netty.util.internal.StringUtil;

/**
 * NettyWebSocketReconnectRunnable
 *
 * @author Superb
 * @date 2021/2/9 18:27
 */
public class NettyWebSocketReconnectRunnable implements Runnable {
    private static final String TAG = NettyWebSocketReconnectRunnable.class.getSimpleName();
    private NettyWebSocketClientImpl imService;

    public static final String SCHEMA ="ws";

    public NettyWebSocketReconnectRunnable(NettyWebSocketClientImpl imService) {
        this.imService = imService;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        try {
            // 重连时，释放工作线程组，也就是停止心跳
            imService.getExecutors().destroyWorkLoopGroup();

            // ims未关闭并且网络可用的情况下，才去连接
            while (!imService.isClosed() && imService.isNetworkAvailable()) {
                ImConnectionStatus status;
                if ((status = connect()) == ImConnectionStatus.Connected) {
                    imService.callbackImServiceConnectStatus(status);
                    break;// 连接成功，跳出循环
                }

                if (status == ImConnectionStatus.ConnectFailed
                        || status == ImConnectionStatus.ConnectFailed_IMSClosed
                        || status == ImConnectionStatus.ConnectFailed_ServerListEmpty
                        || status == ImConnectionStatus.ConnectFailed_ServerEmpty
                        || status == ImConnectionStatus.ConnectFailed_ServerIllegitimate
                        || status == ImConnectionStatus.ConnectFailed_NetworkUnavailable) {
                    imService.callbackImServiceConnectStatus(status);

                    if(imService.isClosed() || !imService.isNetworkAvailable()) {
                        return;
                    }
                    // 一个服务器地址列表都连接失败后，说明网络情况可能很差，延时指定时间（重连间隔时间*2）再去进行下一个服务器地址的连接
                    Log.w(TAG, String.format("一个周期连接失败，等待%1$dms后再次尝试重连", imService.getImProperties().getReconnectInterval() * 2));
                    try {
                        Thread.sleep(imService.getImProperties().getReconnectInterval() * 2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }finally {
            // 标识重连任务停止
            imService.setReconnecting(false);
        }
    }
    /**
     * 连接服务器
     * @return 连接状态
     */
    private ImConnectionStatus connect() {
        if (imService.isClosed()) {
            return ImConnectionStatus.ConnectFailed_IMSClosed;
        }
        if(!imService.isNetworkAvailable()) {
            return ImConnectionStatus.ConnectFailed_NetworkUnavailable;
        }

        String host=imService.getImProperties().getHost();
        int port=imService.getImProperties().getPort();
        if (host == null) {
            return ImConnectionStatus.ConnectFailed_ServerListEmpty;
        }

        imService.initBootstrap();
        if (StringUtil.isNullOrEmpty(host)) {
            return ImConnectionStatus.ConnectFailed_ServerEmpty;
        }

        URI uri;
        try {
            uri = URI.create(host+":"+port);
        }catch (IllegalArgumentException e) {
            e.printStackTrace();
            Log.w(TAG, String.format("【%1$s】连接失败，地址不合法", host));
            return ImConnectionStatus.ConnectFailed_ServerIllegitimate;
        }

        if(!SCHEMA.equals(uri.getScheme())) {
            Log.w(TAG, String.format("【%1$s】连接失败，地址不合法", host));
            return ImConnectionStatus.ConnectFailed_ServerIllegitimate;
        }

        imService.callbackImServiceConnectStatus(ImConnectionStatus.Connecting);

        // +1是因为首次连接也认为是重连，所以如果重连次数设置为3，则最大连接次数为3+1次
        for (int j = 0; j < imService.getImProperties().getReconnectCount() + 1; j++) {
            if (imService.isClosed()) {
                return ImConnectionStatus.ConnectFailed_IMSClosed;
            }
            if (!imService.isNetworkAvailable()) {
                return ImConnectionStatus.ConnectFailed_NetworkUnavailable;
            }

            Log.d(TAG, String.format("正在进行【%1$s】的第%2$d次连接", host, j + 1));
            try {
                String uriHost = uri.getHost();
                int uriPort = uri.getPort();
                Log.d("主机",uriHost);
                Log.d("端口", String.valueOf(uriPort));
                Channel channel = toServer(uriHost, uriPort);
                if (channel != null && channel.isOpen() && channel.isActive() && channel.isRegistered() && channel.isWritable()) {
                    imService.setChannel(channel);
                    return ImConnectionStatus.Connected;
                } else {
                    if (j == imService.getImProperties().getReconnectCount()) {
                        // 如果当前已达到最大重连次数，则回调连接失败
                        Log.w(TAG, String.format("【%1$s】连接失败", uriHost));
                        return ImConnectionStatus.ConnectFailed;
                    } else {
                        // 连接失败，则线程休眠（重连间隔时长 / 2 * n） ms
                        int delayTime = imService.getImProperties().getReconnectInterval() + imService.getImProperties().getReconnectInterval() / 2 * j;
                        Log.w(TAG, String.format("【%1$s】连接失败，正在等待重连，当前重连延时时长：%2$dms", uriHost, delayTime));
                        Thread.sleep(delayTime);
                    }
                }
            } catch (InterruptedException e) {
                break;// 线程被中断，则强制关闭
            }
        }

        return ImConnectionStatus.ConnectFailed;
    }
    /**
     * 真正连接服务器的地方
     * @param host 服务器ip
     * @param port 端口号
     * @return 连接通道
     */
    private Channel toServer(String host, int port) {
        Channel channel;
        try {
            channel = imService.getBootstrap().connect(host, port).sync().channel();
            Log.d(TAG, "toServer: "+channel);
        } catch (Exception e) {
            e.printStackTrace();
            channel = null;
        }

        return channel;
    }
}
